{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/tomasonjo/blogs/blob/master/llm/evaluating_cypher.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "!pip install langchain openai neo4j langchain_openai textdistance"
      ],
      "metadata": {
        "id": "RSemD1s75w11",
        "outputId": "7b7134f9-d406-4f00-9d72-1c133d00fee6",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "id": "RSemD1s75w11",
      "execution_count": 1,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Requirement already satisfied: langchain in /usr/local/lib/python3.10/dist-packages (0.1.1)\n",
            "Requirement already satisfied: openai in /usr/local/lib/python3.10/dist-packages (1.8.0)\n",
            "Requirement already satisfied: neo4j in /usr/local/lib/python3.10/dist-packages (5.16.0)\n",
            "Requirement already satisfied: langchain_openai in /usr/local/lib/python3.10/dist-packages (0.0.2.post1)\n",
            "Requirement already satisfied: textdistance in /usr/local/lib/python3.10/dist-packages (4.6.1)\n",
            "Requirement already satisfied: PyYAML>=5.3 in /usr/local/lib/python3.10/dist-packages (from langchain) (6.0.1)\n",
            "Requirement already satisfied: SQLAlchemy<3,>=1.4 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.0.24)\n",
            "Requirement already satisfied: aiohttp<4.0.0,>=3.8.3 in /usr/local/lib/python3.10/dist-packages (from langchain) (3.9.1)\n",
            "Requirement already satisfied: async-timeout<5.0.0,>=4.0.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (4.0.3)\n",
            "Requirement already satisfied: dataclasses-json<0.7,>=0.5.7 in /usr/local/lib/python3.10/dist-packages (from langchain) (0.6.3)\n",
            "Requirement already satisfied: jsonpatch<2.0,>=1.33 in /usr/local/lib/python3.10/dist-packages (from langchain) (1.33)\n",
            "Requirement already satisfied: langchain-community<0.1,>=0.0.13 in /usr/local/lib/python3.10/dist-packages (from langchain) (0.0.13)\n",
            "Requirement already satisfied: langchain-core<0.2,>=0.1.9 in /usr/local/lib/python3.10/dist-packages (from langchain) (0.1.12)\n",
            "Requirement already satisfied: langsmith<0.1.0,>=0.0.77 in /usr/local/lib/python3.10/dist-packages (from langchain) (0.0.82)\n",
            "Requirement already satisfied: numpy<2,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain) (1.23.5)\n",
            "Requirement already satisfied: pydantic<3,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain) (1.10.13)\n",
            "Requirement already satisfied: requests<3,>=2 in /usr/local/lib/python3.10/dist-packages (from langchain) (2.31.0)\n",
            "Requirement already satisfied: tenacity<9.0.0,>=8.1.0 in /usr/local/lib/python3.10/dist-packages (from langchain) (8.2.3)\n",
            "Requirement already satisfied: anyio<5,>=3.5.0 in /usr/local/lib/python3.10/dist-packages (from openai) (3.7.1)\n",
            "Requirement already satisfied: distro<2,>=1.7.0 in /usr/lib/python3/dist-packages (from openai) (1.7.0)\n",
            "Requirement already satisfied: httpx<1,>=0.23.0 in /usr/local/lib/python3.10/dist-packages (from openai) (0.26.0)\n",
            "Requirement already satisfied: sniffio in /usr/local/lib/python3.10/dist-packages (from openai) (1.3.0)\n",
            "Requirement already satisfied: tqdm>4 in /usr/local/lib/python3.10/dist-packages (from openai) (4.66.1)\n",
            "Requirement already satisfied: typing-extensions<5,>=4.7 in /usr/local/lib/python3.10/dist-packages (from openai) (4.9.0)\n",
            "Requirement already satisfied: pytz in /usr/local/lib/python3.10/dist-packages (from neo4j) (2023.3.post1)\n",
            "Requirement already satisfied: tiktoken<0.6.0,>=0.5.2 in /usr/local/lib/python3.10/dist-packages (from langchain_openai) (0.5.2)\n",
            "Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (23.2.0)\n",
            "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (6.0.4)\n",
            "Requirement already satisfied: yarl<2.0,>=1.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.9.4)\n",
            "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.4.1)\n",
            "Requirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain) (1.3.1)\n",
            "Requirement already satisfied: idna>=2.8 in /usr/local/lib/python3.10/dist-packages (from anyio<5,>=3.5.0->openai) (3.6)\n",
            "Requirement already satisfied: exceptiongroup in /usr/local/lib/python3.10/dist-packages (from anyio<5,>=3.5.0->openai) (1.2.0)\n",
            "Requirement already satisfied: marshmallow<4.0.0,>=3.18.0 in /usr/local/lib/python3.10/dist-packages (from dataclasses-json<0.7,>=0.5.7->langchain) (3.20.2)\n",
            "Requirement already satisfied: typing-inspect<1,>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from dataclasses-json<0.7,>=0.5.7->langchain) (0.9.0)\n",
            "Requirement already satisfied: certifi in /usr/local/lib/python3.10/dist-packages (from httpx<1,>=0.23.0->openai) (2023.11.17)\n",
            "Requirement already satisfied: httpcore==1.* in /usr/local/lib/python3.10/dist-packages (from httpx<1,>=0.23.0->openai) (1.0.2)\n",
            "Requirement already satisfied: h11<0.15,>=0.13 in /usr/local/lib/python3.10/dist-packages (from httpcore==1.*->httpx<1,>=0.23.0->openai) (0.14.0)\n",
            "Requirement already satisfied: jsonpointer>=1.9 in /usr/local/lib/python3.10/dist-packages (from jsonpatch<2.0,>=1.33->langchain) (2.4)\n",
            "Requirement already satisfied: packaging<24.0,>=23.2 in /usr/local/lib/python3.10/dist-packages (from langchain-core<0.2,>=0.1.9->langchain) (23.2)\n",
            "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (3.3.2)\n",
            "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain) (2.0.7)\n",
            "Requirement already satisfied: greenlet!=0.4.17 in /usr/local/lib/python3.10/dist-packages (from SQLAlchemy<3,>=1.4->langchain) (3.0.3)\n",
            "Requirement already satisfied: regex>=2022.1.18 in /usr/local/lib/python3.10/dist-packages (from tiktoken<0.6.0,>=0.5.2->langchain_openai) (2023.6.3)\n",
            "Requirement already satisfied: mypy-extensions>=0.3.0 in /usr/local/lib/python3.10/dist-packages (from typing-inspect<1,>=0.4.0->dataclasses-json<0.7,>=0.5.7->langchain) (1.0.0)\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "# Evaluating LLMs in Cypher Statement Generation\n",
        "## Step-by-step tutorial for assessing the accuracy of generated Cypher Statements\n",
        "\n",
        "Shortly after large language models (LLMs) became popular, we realized they were decent at translating natural language to database queries such as SQL and Cypher. To enable the LLM to create tailored queries for your particular database, you must provide its schema and, optionally, a few example queries. With this information, the LLM can generate database queries based on natural language input.\n",
        "While LLMs show great potential at translating natural language to database queries, they are far from perfect. Therefore, it is essential to understand how well they perform using an evaluation process. Luckily, the process of generating SQL statements has been researched by academia in studies like the Spider. We will use the following metrics to evaluate the Cypher generation ability of LLMs.\n",
        "* **Jaro-Winkler**: This is a text similarity metric based on edit distance. We compare the produced Cypher query to a correct Cypher query, and measure how different the strings are, by how much one would have to edit one query to be the same as the other query\n",
        "* **Pass@1**: This score is 1.0 if the produced query returns the same results from the database as the correct Cypher would, and 0.0 otherwise.\n",
        "* **Pass@3**: Similar to Pass@1, but instead of generating 1 query, we generate 3 queries. If any of them produce the same results as the correct query the score is 1.0, otherwise 0.0\n",
        "* **Jaccard similarity**: It measures the Jaccard similarity between the response returned by the produced Cypher and the correct Cypher's response. This metric is used in mind to capture examples where the model may return almost correct results.\n",
        "\n",
        "As you can observe, the focus is on evaluating responses from the database and not the actual Cypher statement itself. One reason is that a Cypher statement can be written multiple ways to retrieve identical information. We don't care which syntax the LLM prefers; we only care that it produces correct responses. Additionally, we don't have a strong preference for how the LLM names the column in responses, and therefore, we don't want to evaluate its column naming abilities, etc…"
      ],
      "metadata": {
        "id": "c-KR5ezXpWYm"
      },
      "id": "c-KR5ezXpWYm"
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "a7887670-0c56-44a7-9d82-39035fec541f",
      "metadata": {
        "id": "a7887670-0c56-44a7-9d82-39035fec541f"
      },
      "outputs": [],
      "source": [
        "import os\n",
        "import pandas as pd\n",
        "from typing import Set, Any, Union, Dict, List, Tuple, Hashable\n",
        "from langchain_community.graphs import Neo4jGraph\n",
        "from langchain_openai import ChatOpenAI\n",
        "from langchain.chains.graph_qa.cypher_utils import CypherQueryCorrector, Schema\n",
        "from langchain.prompts import ChatPromptTemplate\n",
        "from langchain.schema.runnable import RunnablePassthrough\n",
        "from langchain.schema.output_parser import StrOutputParser\n",
        "import seaborn as sns\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import textdistance\n",
        "from tqdm.auto import tqdm\n",
        "\n",
        "tqdm.pandas()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "e2ef3cff-9aa3-4629-8677-7ca8c0127bf0",
      "metadata": {
        "id": "e2ef3cff-9aa3-4629-8677-7ca8c0127bf0"
      },
      "outputs": [],
      "source": [
        "def get_jw_distance(string1: str, string2: str) -> float:\n",
        "    \"\"\"\n",
        "    Calculate the Jaro-Winkler distance between two strings.\n",
        "\n",
        "    The Jaro-Winkler distance is a measure of similarity between two strings.\n",
        "    The score is normalized such that 0 equates to no similarity and\n",
        "    1 is an exact match.\n",
        "    \"\"\"\n",
        "    # Call the jaro_winkler function from the textdistance library.\n",
        "    return textdistance.jaro_winkler(string1, string2)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "10ed7913-1e32-4f81-8688-9e889d668a96",
      "metadata": {
        "id": "10ed7913-1e32-4f81-8688-9e889d668a96"
      },
      "outputs": [],
      "source": [
        "def rowsim(setL: Set, setR: Set) -> float:\n",
        "    \"\"\"\n",
        "    Calculate the similarity between two sets using Jaccard index formula.\n",
        "    \"\"\"\n",
        "    return len(setL.intersection(setR)) / len(setL.union(setR))\n",
        "\n",
        "\n",
        "def floatify(v: Any) -> Any:\n",
        "    \"\"\"\n",
        "    Attempts to convert a value to a float if it is a string and represents a\n",
        "    number, or recursively apply the conversion to elements within a list or dict.\n",
        "    \"\"\"\n",
        "    if isinstance(v, str):\n",
        "        return v\n",
        "    try:\n",
        "        f = float(v)\n",
        "        return f\n",
        "    except:\n",
        "        pass\n",
        "    if isinstance(v, list):\n",
        "        return [floatify(x) for x in v]\n",
        "    if isinstance(v, dict):\n",
        "        return {k: floatify(u) for k, u in v.items()}\n",
        "    return v\n",
        "\n",
        "\n",
        "def make_hashable(v: Any) -> Hashable:\n",
        "    \"\"\"\n",
        "    Convert a value to a hashable type (needed for set operations).\n",
        "    \"\"\"\n",
        "    float_v = floatify(v)\n",
        "    if not isinstance(float_v, Hashable):\n",
        "        return str(float_v)\n",
        "    else:\n",
        "        return float_v\n",
        "\n",
        "\n",
        "def make_alignment(dictL: List[Dict], dictR: List[Dict]) -> Tuple[List[Set], List[Set]]:\n",
        "    \"\"\"\n",
        "    Align rows from two lists of dictionaries based on their similarity.\n",
        "    \"\"\"\n",
        "    swap = len(dictL) > len(dictR)\n",
        "\n",
        "    # Forming set views from the list of dictionaries.\n",
        "    setViewsL = [{make_hashable(v) for k, v in row.items()} for row in dictL]\n",
        "    setViewsR = [{make_hashable(v) for k, v in row.items()} for row in dictR]\n",
        "    if swap:\n",
        "        setViewsL, setViewsR = setViewsR, setViewsL\n",
        "\n",
        "    for i in range(len(setViewsL)):\n",
        "        max_sim = -1\n",
        "        max_j = -1\n",
        "        for j in range(i, len(setViewsR)):\n",
        "            sim = rowsim(setViewsL[i], setViewsR[j])\n",
        "            if sim > max_sim:\n",
        "                max_j = j\n",
        "                max_sim = sim\n",
        "        tmp = setViewsR[i]\n",
        "        setViewsR[i] = setViewsR[max_j]\n",
        "        setViewsR[max_j] = tmp\n",
        "    if swap:\n",
        "        setViewsL, setViewsR = setViewsR, setViewsL\n",
        "    return setViewsL, setViewsR\n",
        "\n",
        "\n",
        "def df_sim(dictL: List[Dict], dictR: List[Dict], list_view: bool) -> float:\n",
        "    \"\"\"\n",
        "    Calculate the data frame similarity based on either the original row order or an alignment.\n",
        "    \"\"\"\n",
        "    if list_view:\n",
        "        # Original row order for lists of dictionaries\n",
        "        view_L = [row.values() for row in dictL]\n",
        "        view_R = [row.values() for row in dictR]\n",
        "    else:\n",
        "        view_L, view_R = make_alignment(dictL, dictR)\n",
        "\n",
        "    totalSetL = set()\n",
        "    for i, s in enumerate(view_L):\n",
        "        for elem in s:\n",
        "            totalSetL.add((i, make_hashable(elem)))\n",
        "    totalSetR = set()\n",
        "    for i, s in enumerate(view_R):\n",
        "        for elem in s:\n",
        "            totalSetR.add((i, make_hashable(elem)))\n",
        "    intersection = totalSetL.intersection(totalSetR)\n",
        "    union = totalSetL.union(totalSetR)\n",
        "\n",
        "    if len(union) == 0 and len(intersection) == 0:\n",
        "        return 1.0\n",
        "    elif len(union) == 0:\n",
        "        return 0.0\n",
        "\n",
        "    return len(intersection) / len(union)\n",
        "\n",
        "\n",
        "def df_sim_pair(pair_L, pair_R):\n",
        "    \"\"\"\n",
        "    Compute the Jaccard similarity of two data frames (lists of dictionaries),\n",
        "    taking into account the order of rows if indicated by the involved Cypher queries.\n",
        "    \"\"\"\n",
        "    cypher_L, dict_L = pair_L\n",
        "    cypher_R, dict_R = pair_R\n",
        "\n",
        "    return df_sim(dict_L, dict_R, \"order by\" in f\"{cypher_L} {cypher_R}\".lower())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "c2afb653-4127-4a2b-bed7-e68d4b715c00",
      "metadata": {
        "id": "c2afb653-4127-4a2b-bed7-e68d4b715c00",
        "outputId": "41a8930f-ec53-4cca-bd10-4ecaadd0aa5c",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "0.6666666666666666\n"
          ]
        }
      ],
      "source": [
        "# Example usage (different results)\n",
        "dict_a = [{\"id\": 2, \"value\": \"B\"}, {\"id\": 1, \"value\": \"A\"}, {\"id\": 1, \"value\": \"A\"}]\n",
        "dict_b = [{\"id\": 1, \"value\": \"A\"}, {\"id\": 2, \"value\": \"B\"}]\n",
        "\n",
        "query_a = \"MATCH (n:TableA) RETURN n.id AS id, n.value AS value\"\n",
        "query_b = \"MATCH (n:TableB) RETURN n.id AS id, n.value AS value\"\n",
        "\n",
        "similarity_score = df_sim_pair((query_a, dict_a), (query_b, dict_b))\n",
        "print(similarity_score)"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Test dataset\n",
        "The test dataset consists of question and relevant Cypher statement pairs.\n",
        "\n",
        "In this example, I used GPT-4 to come up with suggestions for the training dataset, and then went through them and corrected them where needed. We will use only 27 testing pairs. In practice, you probably want to use at least a couple hundred examples."
      ],
      "metadata": {
        "id": "_wBl50B_pn7x"
      },
      "id": "_wBl50B_pn7x"
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "b03633c5-2ccd-4c08-b000-3a21ebe497e2",
      "metadata": {
        "id": "b03633c5-2ccd-4c08-b000-3a21ebe497e2"
      },
      "outputs": [],
      "source": [
        "# Create a list of dictionaries, each representing a row in the DataFrame\n",
        "data = [\n",
        "    {\n",
        "        \"question\": \"How many movies were released in 1995?\",\n",
        "        \"cypher\": \"MATCH (m:Movie) WHERE m.Year = 1995 RETURN count(*) AS result\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"Who directed the movie Inception?\",\n",
        "        \"cypher\": \"MATCH (m:Movie {title: 'Inception'})<-[:DIRECTED]-(d) RETURN d.name\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"Which actors played in the movie Casino?\",\n",
        "        \"cypher\": \"MATCH (m:Movie {title: 'Casino'})<-[:ACTED_IN]-(a) RETURN a.name\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"How many movies has Tom Hanks acted in?\",\n",
        "        \"cypher\": \"MATCH (a:Actor {name: 'Tom Hanks'})-[:ACTED_IN]->(m:Movie) RETURN count(m)\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"List all the genres of the movie Schindler's List\",\n",
        "        \"cypher\": \"MATCH (m:Movie {title: 'Schindler\\\\'s List'})-[:IN_GENRE]->(g:Genre) RETURN g.name\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"Which actors have worked in movies from both the comedy and action genres?\",\n",
        "        \"cypher\": \"MATCH (a:Actor)-[:ACTED_IN]->(:Movie)-[:IN_GENRE]->(g1:Genre), (a)-[:ACTED_IN]->(:Movie)-[:IN_GENRE]->(g2:Genre) WHERE g1.name = 'Comedy' AND g2.name = 'Action' RETURN DISTINCT a.name\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"List movies that have an IMDb rating above 8.0 and have grossed over 100 million dollars.\",\n",
        "        \"cypher\": \"MATCH (m:Movie) WHERE m.imdbRating > 8.0 AND m.revenue > 100000000 RETURN m.title, m.imdbRating, m.revenue\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"Find the top 3 movies with the highest budget in 1995.\",\n",
        "        \"cypher\": \"MATCH (m:Movie) WHERE m.year = 1995 RETURN m.title, m.budget ORDER BY m.budget DESC LIMIT 3\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"Which directors have made movies with at least three different actors named 'John'?\",\n",
        "        \"cypher\": \"MATCH (d:Director)-[:DIRECTED]->(m:Movie)<-[:ACTED_IN]-(a:Actor) WHERE a.name STARTS WITH 'John' WITH d, COUNT(DISTINCT a) AS JohnsCount WHERE JohnsCount >= 3 RETURN d.name\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"Identify movies where directors also played a role in the film.\",\n",
        "        \"cypher\": \"MATCH (p:Person)-[:DIRECTED]->(m:Movie), (p)-[:ACTED_IN]->(m) RETURN m.title, p.name\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"What is the total box-office revenue of all movies released in the 'Fantasy' genre?\",\n",
        "        \"cypher\": \"MATCH (:Genre {name: 'Fantasy'})<-[:IN_GENRE]-(m:Movie) RETURN SUM(m.revenue) AS TotalRevenue\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"List all users who rated the same movie more than once.\",\n",
        "        \"cypher\": \"MATCH (u:User)-[r:RATED]->(m:Movie) WITH u, m, COUNT(r) AS ratingsCount WHERE ratingsCount > 1 RETURN u.name, m.title, ratingsCount\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"Find the actor with the highest number of movies in the database.\",\n",
        "        \"cypher\": \"MATCH (a:Actor)-[:ACTED_IN]->(m:Movie) RETURN a.name, COUNT(m) AS movieCount ORDER BY movieCount DESC LIMIT 1\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"Which movies have a plot that mentions 'revenge' and were released after 2010?\",\n",
        "        \"cypher\": \"MATCH (m:Movie) WHERE m.plot CONTAINS 'revenge' AND m.year > 2010 RETURN m.title, m.released\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"How many movies are there in each genre?\",\n",
        "        \"cypher\": \"MATCH (g:Genre)<-[:IN_GENRE]-(m:Movie) RETURN g.name, COUNT(m) AS movieCount\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"What are the names of actors born in 'Dallas, Texas, USA' who have acted in Mystery movies?\",\n",
        "        \"cypher\": \"MATCH (a:Actor {bornIn: 'London'})-[:ACTED_IN]->(:Movie)-[:IN_GENRE]->(:Genre {name: 'Mystery'}) RETURN DISTINCT a.name\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"Identify the movie with the longest runtime that was released in the 2000s.\",\n",
        "        \"cypher\": \"MATCH (m:Movie) WHERE m.year >= 2000 AND m.year < 2010 RETURN m.title, m.runtime ORDER BY m.runtime DESC LIMIT 1\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"List the languages available in movies directed by 'Steven Spielberg'.\",\n",
        "        \"cypher\": \"MATCH (d:Director {name: 'Steven Spielberg'})-[:DIRECTED]->(m:Movie) UNWIND m.languages AS language RETURN DISTINCT language\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"Which movie had the highest IMDb rating in the year Quentin Tarantino was born?\",\n",
        "        \"cypher\": \"MATCH (d:Director {name: 'Quentin Tarantino'}), (m:Movie) WHERE m.year = d.born.year RETURN m.title, m.imdbRating ORDER BY m.imdbRating DESC LIMIT 1\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"What are the most common genres for movies with a budget over 200 million dollars?\",\n",
        "        \"cypher\": \"MATCH (m:Movie)-[:IN_GENRE]->(g:Genre) WHERE m.budget > 200000000 RETURN g.name, COUNT(*) AS genreCount ORDER BY genreCount DESC\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"Which countries are represented by at least five movies in the database?\",\n",
        "        \"cypher\": \"MATCH (m:Movie) UNWIND m.countries AS country WITH country, COUNT(m) AS movieCount WHERE movieCount >= 5 RETURN country, movieCount\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"List all movies that involve time travel in their plot.\",\n",
        "        \"cypher\": \"MATCH (m:Movie) WHERE m.plot CONTAINS 'time travel' RETURN m.title\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"What are the average IMDb ratings for each year in the 21st century?\",\n",
        "        \"cypher\": \"MATCH (m:Movie) WHERE m.year >= 2000 AND m.year < 2100 WITH m.year AS year, AVG(m.imdbRating) AS avgRating RETURN year, avgRating ORDER BY year\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"List top 10 actors with the most diverse genres in their filmography.\",\n",
        "        \"cypher\": \"MATCH (a:Actor)-[:ACTED_IN]->(:Movie)-[:IN_GENRE]->(g:Genre) WITH a, COUNT(DISTINCT g) AS genreDiversity ORDER BY genreDiversity DESC LIMIT 10 RETURN a.name, genreDiversity\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"Which directors have never had a movie with a rating below 6.0?\",\n",
        "        \"cypher\": \"MATCH (d:Director)-[:DIRECTED]->(m:Movie) WITH d, MIN(m.imdbRating) AS lowestRating WHERE lowestRating >= 6.0 RETURN d.name, lowestRating\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"How many movies have the keyword 'love' in the title and a runtime under 2 hours?\",\n",
        "        \"cypher\": \"MATCH (m:Movie) WHERE m.title CONTAINS 'love' AND m.runtime < 120 RETURN COUNT(m) AS numberOfMovies\",\n",
        "    },\n",
        "    {\n",
        "        \"question\": \"Return the list of movies that have a higher IMDb rating than any of 'Tom Hanks' movies.\",\n",
        "        \"cypher\": \"MATCH (m:Movie), (th:Actor {name: 'Tom Hanks'})-[:ACTED_IN]->(tomHanksMovie:Movie) WITH MAX(tomHanksMovie.imdbRating) AS maxTomHanksRating MATCH (m) WHERE m.imdbRating > maxTomHanksRating RETURN m.title, m.imdbRating\",\n",
        "    },\n",
        "]\n",
        "\n",
        "# Create the DataFrame\n",
        "df = pd.DataFrame(data)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "73c48335-13cc-45b1-9a39-38ac9399171b",
      "metadata": {
        "id": "73c48335-13cc-45b1-9a39-38ac9399171b",
        "outputId": "3055a70c-7331-4665-b14c-166c69265944",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 206
        }
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "                                            question  \\\n",
              "0             How many movies were released in 1995?   \n",
              "1                  Who directed the movie Inception?   \n",
              "2           Which actors played in the movie Casino?   \n",
              "3            How many movies has Tom Hanks acted in?   \n",
              "4  List all the genres of the movie Schindler's List   \n",
              "\n",
              "                                              cypher  \n",
              "0  MATCH (m:Movie) WHERE m.Year = 1995 RETURN cou...  \n",
              "1  MATCH (m:Movie {title: 'Inception'})<-[:DIRECT...  \n",
              "2  MATCH (m:Movie {title: 'Casino'})<-[:ACTED_IN]...  \n",
              "3  MATCH (a:Actor {name: 'Tom Hanks'})-[:ACTED_IN...  \n",
              "4  MATCH (m:Movie {title: 'Schindler\\'s List'})-[...  "
            ],
            "text/html": [
              "\n",
              "  <div id=\"df-e087260a-8e95-4f71-af05-84450fa46bf4\" class=\"colab-df-container\">\n",
              "    <div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "      <th>question</th>\n",
              "      <th>cypher</th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "    <tr>\n",
              "      <th>0</th>\n",
              "      <td>How many movies were released in 1995?</td>\n",
              "      <td>MATCH (m:Movie) WHERE m.Year = 1995 RETURN cou...</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>1</th>\n",
              "      <td>Who directed the movie Inception?</td>\n",
              "      <td>MATCH (m:Movie {title: 'Inception'})&lt;-[:DIRECT...</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>2</th>\n",
              "      <td>Which actors played in the movie Casino?</td>\n",
              "      <td>MATCH (m:Movie {title: 'Casino'})&lt;-[:ACTED_IN]...</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>3</th>\n",
              "      <td>How many movies has Tom Hanks acted in?</td>\n",
              "      <td>MATCH (a:Actor {name: 'Tom Hanks'})-[:ACTED_IN...</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>4</th>\n",
              "      <td>List all the genres of the movie Schindler's List</td>\n",
              "      <td>MATCH (m:Movie {title: 'Schindler\\'s List'})-[...</td>\n",
              "    </tr>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>\n",
              "    <div class=\"colab-df-buttons\">\n",
              "\n",
              "  <div class=\"colab-df-container\">\n",
              "    <button class=\"colab-df-convert\" onclick=\"convertToInteractive('df-e087260a-8e95-4f71-af05-84450fa46bf4')\"\n",
              "            title=\"Convert this dataframe to an interactive table.\"\n",
              "            style=\"display:none;\">\n",
              "\n",
              "  <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 -960 960 960\">\n",
              "    <path d=\"M120-120v-720h720v720H120Zm60-500h600v-160H180v160Zm220 220h160v-160H400v160Zm0 220h160v-160H400v160ZM180-400h160v-160H180v160Zm440 0h160v-160H620v160ZM180-180h160v-160H180v160Zm440 0h160v-160H620v160Z\"/>\n",
              "  </svg>\n",
              "    </button>\n",
              "\n",
              "  <style>\n",
              "    .colab-df-container {\n",
              "      display:flex;\n",
              "      gap: 12px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert {\n",
              "      background-color: #E8F0FE;\n",
              "      border: none;\n",
              "      border-radius: 50%;\n",
              "      cursor: pointer;\n",
              "      display: none;\n",
              "      fill: #1967D2;\n",
              "      height: 32px;\n",
              "      padding: 0 0 0 0;\n",
              "      width: 32px;\n",
              "    }\n",
              "\n",
              "    .colab-df-convert:hover {\n",
              "      background-color: #E2EBFA;\n",
              "      box-shadow: 0px 1px 2px rgba(60, 64, 67, 0.3), 0px 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "      fill: #174EA6;\n",
              "    }\n",
              "\n",
              "    .colab-df-buttons div {\n",
              "      margin-bottom: 4px;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert {\n",
              "      background-color: #3B4455;\n",
              "      fill: #D2E3FC;\n",
              "    }\n",
              "\n",
              "    [theme=dark] .colab-df-convert:hover {\n",
              "      background-color: #434B5C;\n",
              "      box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.15);\n",
              "      filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3));\n",
              "      fill: #FFFFFF;\n",
              "    }\n",
              "  </style>\n",
              "\n",
              "    <script>\n",
              "      const buttonEl =\n",
              "        document.querySelector('#df-e087260a-8e95-4f71-af05-84450fa46bf4 button.colab-df-convert');\n",
              "      buttonEl.style.display =\n",
              "        google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "\n",
              "      async function convertToInteractive(key) {\n",
              "        const element = document.querySelector('#df-e087260a-8e95-4f71-af05-84450fa46bf4');\n",
              "        const dataTable =\n",
              "          await google.colab.kernel.invokeFunction('convertToInteractive',\n",
              "                                                    [key], {});\n",
              "        if (!dataTable) return;\n",
              "\n",
              "        const docLinkHtml = 'Like what you see? Visit the ' +\n",
              "          '<a target=\"_blank\" href=https://colab.research.google.com/notebooks/data_table.ipynb>data table notebook</a>'\n",
              "          + ' to learn more about interactive tables.';\n",
              "        element.innerHTML = '';\n",
              "        dataTable['output_type'] = 'display_data';\n",
              "        await google.colab.output.renderOutput(dataTable, element);\n",
              "        const docLink = document.createElement('div');\n",
              "        docLink.innerHTML = docLinkHtml;\n",
              "        element.appendChild(docLink);\n",
              "      }\n",
              "    </script>\n",
              "  </div>\n",
              "\n",
              "\n",
              "<div id=\"df-559a0a0a-1862-4286-8aec-a1167b96d3df\">\n",
              "  <button class=\"colab-df-quickchart\" onclick=\"quickchart('df-559a0a0a-1862-4286-8aec-a1167b96d3df')\"\n",
              "            title=\"Suggest charts\"\n",
              "            style=\"display:none;\">\n",
              "\n",
              "<svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\"viewBox=\"0 0 24 24\"\n",
              "     width=\"24px\">\n",
              "    <g>\n",
              "        <path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z\"/>\n",
              "    </g>\n",
              "</svg>\n",
              "  </button>\n",
              "\n",
              "<style>\n",
              "  .colab-df-quickchart {\n",
              "      --bg-color: #E8F0FE;\n",
              "      --fill-color: #1967D2;\n",
              "      --hover-bg-color: #E2EBFA;\n",
              "      --hover-fill-color: #174EA6;\n",
              "      --disabled-fill-color: #AAA;\n",
              "      --disabled-bg-color: #DDD;\n",
              "  }\n",
              "\n",
              "  [theme=dark] .colab-df-quickchart {\n",
              "      --bg-color: #3B4455;\n",
              "      --fill-color: #D2E3FC;\n",
              "      --hover-bg-color: #434B5C;\n",
              "      --hover-fill-color: #FFFFFF;\n",
              "      --disabled-bg-color: #3B4455;\n",
              "      --disabled-fill-color: #666;\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart {\n",
              "    background-color: var(--bg-color);\n",
              "    border: none;\n",
              "    border-radius: 50%;\n",
              "    cursor: pointer;\n",
              "    display: none;\n",
              "    fill: var(--fill-color);\n",
              "    height: 32px;\n",
              "    padding: 0;\n",
              "    width: 32px;\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart:hover {\n",
              "    background-color: var(--hover-bg-color);\n",
              "    box-shadow: 0 1px 2px rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15);\n",
              "    fill: var(--button-hover-fill-color);\n",
              "  }\n",
              "\n",
              "  .colab-df-quickchart-complete:disabled,\n",
              "  .colab-df-quickchart-complete:disabled:hover {\n",
              "    background-color: var(--disabled-bg-color);\n",
              "    fill: var(--disabled-fill-color);\n",
              "    box-shadow: none;\n",
              "  }\n",
              "\n",
              "  .colab-df-spinner {\n",
              "    border: 2px solid var(--fill-color);\n",
              "    border-color: transparent;\n",
              "    border-bottom-color: var(--fill-color);\n",
              "    animation:\n",
              "      spin 1s steps(1) infinite;\n",
              "  }\n",
              "\n",
              "  @keyframes spin {\n",
              "    0% {\n",
              "      border-color: transparent;\n",
              "      border-bottom-color: var(--fill-color);\n",
              "      border-left-color: var(--fill-color);\n",
              "    }\n",
              "    20% {\n",
              "      border-color: transparent;\n",
              "      border-left-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "    }\n",
              "    30% {\n",
              "      border-color: transparent;\n",
              "      border-left-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "      border-right-color: var(--fill-color);\n",
              "    }\n",
              "    40% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "      border-top-color: var(--fill-color);\n",
              "    }\n",
              "    60% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "    }\n",
              "    80% {\n",
              "      border-color: transparent;\n",
              "      border-right-color: var(--fill-color);\n",
              "      border-bottom-color: var(--fill-color);\n",
              "    }\n",
              "    90% {\n",
              "      border-color: transparent;\n",
              "      border-bottom-color: var(--fill-color);\n",
              "    }\n",
              "  }\n",
              "</style>\n",
              "\n",
              "  <script>\n",
              "    async function quickchart(key) {\n",
              "      const quickchartButtonEl =\n",
              "        document.querySelector('#' + key + ' button');\n",
              "      quickchartButtonEl.disabled = true;  // To prevent multiple clicks.\n",
              "      quickchartButtonEl.classList.add('colab-df-spinner');\n",
              "      try {\n",
              "        const charts = await google.colab.kernel.invokeFunction(\n",
              "            'suggestCharts', [key], {});\n",
              "      } catch (error) {\n",
              "        console.error('Error during call to suggestCharts:', error);\n",
              "      }\n",
              "      quickchartButtonEl.classList.remove('colab-df-spinner');\n",
              "      quickchartButtonEl.classList.add('colab-df-quickchart-complete');\n",
              "    }\n",
              "    (() => {\n",
              "      let quickchartButtonEl =\n",
              "        document.querySelector('#df-559a0a0a-1862-4286-8aec-a1167b96d3df button');\n",
              "      quickchartButtonEl.style.display =\n",
              "        google.colab.kernel.accessAllowed ? 'block' : 'none';\n",
              "    })();\n",
              "  </script>\n",
              "</div>\n",
              "    </div>\n",
              "  </div>\n"
            ]
          },
          "metadata": {},
          "execution_count": 7
        }
      ],
      "source": [
        "df.head()"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "You can use an LLM to generate suggestions for the testing dataset. However, you need to manually validate the examples as the LLMs make mistakes and aren't 100% reliable. If they were, we wouldn't need to test them anyway. As we are evaluating based on database results and not the Cypher statements themselves, we need to have a running database with relevant information that we can use. In this blog post, we will use the recommendations project, which contains movies, actors, ratings, and more information. The recommendations project is also available as readonly access on the demo server, which means you don't have to create a new database instance if you don't want to.\n",
        "\n",
        "We will be using LangChain to generate Cypher statements. The Neo4jGraph object in LangChain establishes the connection to Neo4j and retrieves its schema information."
      ],
      "metadata": {
        "id": "D-ixslsJpuqn"
      },
      "id": "D-ixslsJpuqn"
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "5a4c447d-c4bc-435e-aa4c-766ddff7a8c8",
      "metadata": {
        "id": "5a4c447d-c4bc-435e-aa4c-766ddff7a8c8",
        "outputId": "4fe731d7-9b90-4824-efed-54d7fc26185f",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Node properties are the following:\n",
            "Movie {url: STRING, runtime: INTEGER, revenue: INTEGER, plotEmbedding: LIST, posterEmbedding: LIST, imdbRating: FLOAT, released: STRING, countries: LIST, languages: LIST, plot: STRING, imdbVotes: INTEGER, imdbId: STRING, year: INTEGER, poster: STRING, movieId: STRING, tmdbId: STRING, title: STRING, budget: INTEGER},Genre {name: STRING},User {userId: STRING, name: STRING},Actor {url: STRING, bornIn: STRING, bio: STRING, died: DATE, born: DATE, imdbId: STRING, name: STRING, poster: STRING, tmdbId: STRING},Director {url: STRING, bornIn: STRING, born: DATE, died: DATE, tmdbId: STRING, imdbId: STRING, name: STRING, poster: STRING, bio: STRING},Person {url: STRING, bornIn: STRING, bio: STRING, died: DATE, born: DATE, imdbId: STRING, name: STRING, poster: STRING, tmdbId: STRING}\n",
            "Relationship properties are the following:\n",
            "RATED {rating: FLOAT, timestamp: INTEGER},ACTED_IN {role: STRING},DIRECTED {role: STRING}\n",
            "The relationships are the following:\n",
            "(:Movie)-[:IN_GENRE]->(:Genre),(:User)-[:RATED]->(:Movie),(:Actor)-[:ACTED_IN]->(:Movie),(:Actor)-[:DIRECTED]->(:Movie),(:Director)-[:DIRECTED]->(:Movie),(:Director)-[:ACTED_IN]->(:Movie),(:Person)-[:ACTED_IN]->(:Movie),(:Person)-[:DIRECTED]->(:Movie)\n"
          ]
        }
      ],
      "source": [
        "os.environ[\"NEO4J_URI\"] = \"neo4j+s://demo.neo4jlabs.com\"\n",
        "os.environ[\"NEO4J_USERNAME\"] = \"recommendations\"\n",
        "os.environ[\"NEO4J_PASSWORD\"] = \"recommendations\"\n",
        "os.environ[\"NEO4J_DATABASE\"] = \"recommendations\"\n",
        "\n",
        "graph = Neo4jGraph()\n",
        "print(graph.schema)"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Generating Cypher statements\n",
        "\n",
        "The schema contains node labels, their properties, and the corresponding relationships. Next, we will use the LangChain expression language to define a prompt sent to the LLM with instructions to translate the natural language to a Cypher statement that retrieves relevant information to answer the question."
      ],
      "metadata": {
        "id": "lv7pF5smqAPx"
      },
      "id": "lv7pF5smqAPx"
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "40678298-b99a-4ed2-b1a8-283875ba9a5f",
      "metadata": {
        "id": "40678298-b99a-4ed2-b1a8-283875ba9a5f"
      },
      "outputs": [],
      "source": [
        "os.environ[\"OPENAI_API_KEY\"] = \"sk-\"\n",
        "llm = ChatOpenAI(model_name=\"gpt-4-1106-preview\", temperature=0)\n",
        "# Generate Cypher statement based on natural language input\n",
        "cypher_template = \"\"\"Based on the Neo4j graph schema below,\n",
        "write a Cypher query that would answer the user's question.\n",
        "Return only Cypher statement, no backticks, nothing else.\n",
        "{schema}\n",
        "\n",
        "Question: {question}\n",
        "Cypher query:\"\"\"  # noqa: E501\n",
        "\n",
        "cypher_prompt = ChatPromptTemplate.from_messages(\n",
        "    [\n",
        "        (\n",
        "            \"system\",\n",
        "            \"Given an input question, convert it to a Cypher query. No pre-amble.\",\n",
        "        ),\n",
        "        (\"human\", cypher_template),\n",
        "    ]\n",
        ")\n",
        "\n",
        "cypher_chain = (\n",
        "    RunnablePassthrough.assign(\n",
        "        schema=lambda _: graph.get_schema,\n",
        "    )\n",
        "    | cypher_prompt\n",
        "    | llm.bind(stop=[\"\\nCypherResult:\"])\n",
        "    | StrOutputParser()\n",
        ")\n"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "If you are familiar with conversational LLMs, you can spot the system and human message definitions. As you can observe, we put both the graph schema as well as user question into the human message. The exact prompt engineering instructions to generate Cypher statements is not a solved problem, which means that there could be some improvements made here. Using an evaluation process, you could see what works best for the particular LLM. In this example, we are using gpt-4-turbo.\n",
        "\n",
        "We can test the Cypher generation with the following example:"
      ],
      "metadata": {
        "id": "ocpxacwZqHmf"
      },
      "id": "ocpxacwZqHmf"
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "ea27cf5f-ccd6-4686-8065-4e653a829234",
      "metadata": {
        "id": "ea27cf5f-ccd6-4686-8065-4e653a829234",
        "outputId": "321f2389-2d63-4d9b-e6a4-d4b4e0c5258d",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "MATCH (m:Movie) WHERE m.title CONTAINS 'love' AND m.runtime < 120 RETURN count(m) as movieCount\n"
          ]
        }
      ],
      "source": [
        "response = cypher_chain.invoke(\n",
        "    {\n",
        "        \"question\": \"How many movies have the keyword 'love' in the title and a runtime under 2 hours?\"\n",
        "    }\n",
        ")\n",
        "print(response)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "06807edc-385e-4a69-a530-1d2ca65488da",
      "metadata": {
        "id": "06807edc-385e-4a69-a530-1d2ca65488da"
      },
      "source": [
        "We can observe that gpt-4-turbo is somewhat decent at translating natural language to Cypher statements. Let's now define the evaluation process."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "5d7b9444-675f-458d-ac17-3041373ae02e",
      "metadata": {
        "id": "5d7b9444-675f-458d-ac17-3041373ae02e",
        "outputId": "e612cb5d-04f0-4316-f7ae-fa9af8d7ff54",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 49,
          "referenced_widgets": [
            "ed0715621c1b4a3db11b8733f2b8e1bc",
            "dfeac02f8f7f40669dbc82c4471bfa5f",
            "8bb7f03b990b403ab4c90d0adb35f588",
            "9cec58549df743309a242bf8c1f1f626",
            "a2582b25faed45edac03153dba8bebe3",
            "bbc3e7f360fa4c61af78efe8c616cbae",
            "3c33da6e879a44c4b703c853083063cb",
            "9ce518888c5e43f092b1c5d715fd09b7",
            "7f52c198bdd447798849fc5b75049ec5",
            "edbcf4fdc0b8403ab53b99e21a81aa24",
            "691e71bef0624d97b550ad070a0b7266"
          ]
        }
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "  0%|          | 0/27 [00:00<?, ?it/s]"
            ],
            "application/vnd.jupyter.widget-view+json": {
              "version_major": 2,
              "version_minor": 0,
              "model_id": "ed0715621c1b4a3db11b8733f2b8e1bc"
            }
          },
          "metadata": {}
        }
      ],
      "source": [
        "# Create empty lists to store the results of the new columns\n",
        "generated_cyphers = []\n",
        "true_datas = []\n",
        "eval_datas = []\n",
        "jaro_winklers = []\n",
        "pass_1s = []\n",
        "pass_3s = []\n",
        "jaccards = []\n",
        "\n",
        "# Iterate over each row with tqdm to show a progress bar\n",
        "for index, row in tqdm(df.iterrows(), total=df.shape[0]):\n",
        "    # Fetch data based on the test Cypher statement\n",
        "    true_data = graph.query(row[\"cypher\"])\n",
        "    # Generate 3 Cypher statement and fetch data\n",
        "    example_generated_cyphers = []\n",
        "    example_eval_datas = []\n",
        "    for _ in range(3):\n",
        "        cypher = cypher_chain.invoke({\"question\": row[\"question\"]})\n",
        "        example_generated_cyphers.append(cypher)\n",
        "        # Fetch data based on the generated Cypher statement\n",
        "        try:\n",
        "            example_eval_datas.append(graph.query(cypher))\n",
        "        except ValueError:  # Handle syntax error\n",
        "            example_eval_datas.append([{\"id\": \"Cypher syntax error\"}])\n",
        "\n",
        "    # These metrics require only the first cypher/response\n",
        "    jaro_winkler = get_jw_distance(row[\"cypher\"], example_generated_cyphers[0])\n",
        "    pass_1 = (\n",
        "        1\n",
        "        if df_sim_pair(\n",
        "            (row[\"cypher\"], true_data),\n",
        "            (example_generated_cyphers[0], example_eval_datas[0]),\n",
        "        )\n",
        "        == 1\n",
        "        else 0\n",
        "    )\n",
        "    jaccard = df_sim_pair(\n",
        "        (row[\"cypher\"], true_data),\n",
        "        (example_generated_cyphers[0], example_eval_datas[0]),\n",
        "    )\n",
        "    # Pass@3 check all 3 responses\n",
        "    pass_3 = 1 if any(\n",
        "        df_sim_pair((row[\"cypher\"], true_data), (gen_cypher, eval_data)) == 1\n",
        "        for gen_cypher, eval_data in zip(example_generated_cyphers, example_eval_datas)\n",
        "    ) else 0\n",
        "\n",
        "    # Append the results to their respective lists\n",
        "    generated_cyphers.append(example_generated_cyphers)\n",
        "    true_datas.append(true_data)\n",
        "    eval_datas.append(example_eval_datas)\n",
        "    jaro_winklers.append(jaro_winkler)\n",
        "    pass_1s.append(pass_1)\n",
        "    pass_3s.append(pass_3)\n",
        "    jaccards.append(jaccard)\n",
        "\n",
        "# Add the lists as columns to the DataFrame\n",
        "df[\"generated_cypher\"] = generated_cyphers\n",
        "df[\"true_data\"] = true_datas\n",
        "df[\"eval_data\"] = eval_datas\n",
        "df[\"jaro_winkler\"] = jaro_winklers\n",
        "df[\"pass_1\"] = pass_1s\n",
        "df[\"pass_3\"] = pass_3s\n",
        "df[\"jaccard\"] = jaccards\n"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "*Running this code took about 5 minutes as we need to generate 81 responses to calculate the pass@3 metric.*\n",
        "\n",
        "The code is slightly lengthy. However, the gist is quite simple to understand. We iterate over all the rows in the data frame that stores the testing examples. Next, we generate three Cypher queries for each training example and retrieve corresponding data from the database. What follows is then calculating the relevant metrics and storing them in lists so that we can evaluate and visualize them.\n",
        "\n",
        "Let's now evaluate the results."
      ],
      "metadata": {
        "id": "FG7Oxd-5qSHF"
      },
      "id": "FG7Oxd-5qSHF"
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "364587cb-36b6-4863-9d82-765070b3314a",
      "metadata": {
        "id": "364587cb-36b6-4863-9d82-765070b3314a",
        "outputId": "37073208-28f9-49dd-b4d9-a3190291fb94",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 564
        }
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 1000x600 with 1 Axes>"
            ],
            "image/png": "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vFnbt2+3O/OTlZWlqKgohYWF6b333tOGDRs0btw4BQUFqVevXtet/c0339Tw4cMVGRmpXr16ac+ePZoyZYp++OEHbd68WSVLltSECRP0ySef6IsvvtCUKVPk4eGhunXr3viOK4Ann3xSffv21fnz5+Xh4aErV65oyZIlio2Nzfdletu3b1erVq3k7++v4cOHKysrS2+99ZYqVKiQo+/bb7+tIUOGqH379urevbtOnDihDz/8UM2aNcux/1NTU9WqVSs9+uijat++vT7//HMNHDhQderUUevWrSX9+eG8Xbt22rRpk5577jnVrFlTO3fu1Pvvv6+9e/fmuM/lm2++0WeffaY+ffrI29vbFuInTpyodu3a6amnnlJmZqYWLVqkJ554QqtWrVKbNm0k/TmIR/fu3dWwYUM999xzkqSgoCBJUkpKiho1amQLZxUqVNDatWv17LPP6uzZs3r55ZclSRcvXlSLFi2UlJSkl156SRUrVtS8efP0zTff5Gtff/nll6pataoaN25s2rdz587q0aOH/vvf/9qF4B9++EF79+7VG2+8Ydf/k08+0blz59S7d29dunRJEydO1AMPPKCdO3faHVP5PS569uxpO95eeukl/f7775o0aZK2b99uOwau2rNnjzp16qSePXuqR48eql69eq7blJmZqaioKGVkZOjFF1+Un5+fjhw5olWrVunMmTPy8vK67vP0ww8/aMuWLerYsaPuuOMOHTx4UFOmTNH999+vX3/9VaVKlVKzZs300ksv6YMPPtBrr72mmjVrSpLtv/PmzVOXLl0UFRWlMWPG6MKFC5oyZYruu+8+bd++3e6LoaysLLVu3VrNmjXT2LFjtWDBAvXp00fu7u56/fXX9dRTT+nRRx/V1KlTFRMTo/DwcNvl1BcuXFBERISOHDminj17qnLlytqyZYsGDx6sY8eOacKECXb7ZuHChTp37px69uwpi8WisWPH6tFHH9WBAwdUsmRJ9ezZU0ePHtX69es1b948u3nXr1+vTp06qUWLFhozZowkadeuXdq8ebMtiAP4fwaA29rs2bMNSbk+rFarrd9XX31lSDK+/PJLu/kffPBBo2rVqra/r1y5YmRkZNj1SU1NNXx9fY1nnnnGrl2SMWzYMNvfXbp0MapUqZKjxmHDhhl/fTu6cOFCjn5RUVF2tRiGYdx9991GREREjr4b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vvrqq3J3d1f37t2VkpKSY/pvv/2WYwjpzp07KzU1VT179tT58+ft7hu71vLly+2G5d+6dau+//77HPeq5Uf79u2VlZWlESNG5Jh25cqVfB2vuTl79qyuXLli11anTh05OTnZHdN5vSc4OzvneO19+OGHOc54X73/66/LiIqKkqenp0aNGmV3ifJV1z7XN6p9+/ZKSEjQV199lWPamTNncuyH/Mhru/567Dk5OdnOkuZ3qHyguOCME1BMrF27Vrt3787R3rhxY7szIx06dNCHH36oYcOGqU6dOrZheK966KGHtGzZMj3yyCNq06aNfv/9d02dOlW1atXS+fPnr1tDx44dNXDgQD3yyCN66aWXbEP53nXXXXY3Z7ds2dL2DejVD3wzZsyQj4+Pjh07ZrfMkJAQTZkyRSNHjlRwcLB8fHxyvQehZMmSGjNmjLp166aIiAh16tTJNhx5YGCg+vXrl6/9aKZChQoaPHiwhg8frlatWqldu3bas2ePPvroI9177715fnDNr/Hjx6tUqVJ2bU5OTnrttddsfy9dujTX57pLly45Ll+TpHr16uX5mz9m3nzzTX399ddq0qSJevXqpaysLE2aNEm1a9fWjh07bP2CgoI0cuRIDR48WAcPHlR0dLRKly6t33//XV988YWee+4522+F5Vfnzp312Wef6fnnn9fGjRvVpEkTZWVlaffu3frss89svw10PW3atNH48ePVqlUrPfnkkzp+/LgmT56s4OBgu4Av/fla27Bhg8aPH6+KFSvqzjvvVFhYmN555x1t3LhRYWFh6tGjh2rVqqXTp08rMTFRGzZs0OnTpyX9OUDIpEmTFBMTo23btsnf31/z5s3L8XzmJSgoSAsXLlSHDh1Us2ZNxcTEqHbt2srMzNSWLVu0ZMkSu9+WkqQGDRqodu3atoE07rnnnlyXHRwcrPvuu0+9evVSRkaGJkyYoPLly+d5qdj1REREqGfPnho9erR27Nihli1bqmTJktq3b5+WLFmiiRMn2g2QkV/ffPON+vTpoyeeeEJ33XWXrly5onnz5tnC61V5PU8PPfSQ5s2bJy8vL9WqVUsJCQnasGGDypcvb7ee+vXry9nZWWPGjFFaWpqsVqvtN+WmTJmizp0765577lHHjh1VoUIFJSUlafXq1WrSpIkmTZpU4O3KzYABA7Ry5Uo99NBD6tq1q0JC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QkJC7AZ6yM7OVlxcnMLDw3Od58KFCznOHDk7O0uS/jpWibu7u/z9/ZWamqqvvvpKDz/8cCFvAQAAAGCOM064YbGxserSpYtCQ0PVsGFDTZgwQenp6erWrZskKSYmRpUqVdLo0aMlSW3bttX48ePVoEEDhYWFaf/+/RoyZIjatm1rC1BfffWVDMNQ9erVtX//fg0YMEA1atSwLRMAAAC4mQhOuGEdOnTQiRMnNHToUCUnJ6t+/fpat26dbcCIpKQkuzNMb7zxhiwWi9544w0dOXJEFSpUUNu2bfX222/b+qSlpWnw4ME6fPiwypUrp8cee0xvv/22SpYsedO3DwAAAOB3nAAAAAAUS/yOEwAAAAAUIoITAAAAAJggOAEAAACACYITAAAAAJggOAEAAACACYITAAAAAJggOAEAAACACX4AtxCEDPjE0SWgmNj2boyjSwAAACiWOOMEAAAAACYITgAAAABgguAEAAAAACYITgAAAABgguAEAAAAACYITgAAAABgguAEAAAAACYITgAAAABgguAEAAAAACYITgAAAABgguAEAAAAACYITgAAAABgguAEAAAAACYITgAAAABgguAEAAAAACYITgAAAABgguAEAAAAACYITgAAAABgguAEAAAAACYITgAAAABgguAEAAAAACYITgAAAABgguAEAAAAACYITgAAAABgguAEAAAAACYITgAAAABgguAEAAAAACYITgAAAABgguAEAAAAACYITgAAAABgguAEAAAAACYITgAAAABgguAEAAAAACYITgAAAABgguAEAAAAACYITgAAAABgguAEAAAAACYcHpwmT56swMBAubq6KiwsTFu3br1u/wkTJqh69epyc3NTQECA+vXrp0uXLt2kagEAAAAURw4NTosXL1ZsbKyGDRumxMRE1atXT1FRUTp+/Hiu/RcuXKhBgwZp2LBh2rVrl2bOnKnFixfrtddeu8mVAwAAAChOHBqcxo8frx49eqhbt26qVauWpk6dqlKlSmnWrFm59t+yZYuaNGmiJ598UoGBgWrZsqU6depkepYKAAAAAG6Ew4JTZmamtm3bpsjIyP8V4+SkyMhIJSQk5DpP48aNtW3bNltQOnDggNasWaMHH3wwz/VkZGTo7Nmzdg8AAAAAKIgSjlrxyZMnlZWVJV9fX7t2X19f7d69O9d5nnzySZ08eVL3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TbrrrrvUtm1bu/b4+HhVqVJFd955pyT7S/Wu1rNs2TI1b95cpUqVUr169ZSQkJDnek+cOKHQ0FA98sgjysjIyLXPpk2b1LRpU7m5uSkgIEAvvfSS0tPTbdMDAwM1YsQIxcTEyNPTU88999x1txW3P6vVKj8/P1WpUkW9evVSZGSkVq5cqfHjx6tOnTpyd3dXQECAXnjhBZ0/f94236FDh9S2bVuVLVtW7u7uuvvuu7VmzRpJUmpqqp566ilVqFBBbm5uqlatmmbPnm2b98svv9SAAQP09ddf69NPP9UjjzyievXqKSwsTP3799euXbt0/PjxHJdffPDBB+rdu7eqVq16U/YNiod/yjFQtmxZ9erVS6GhoapSpYpatGihF154Qd9+++1N21coHsw+U0nS4cOH1alTJ5UrV07u7u4KDQ3V999/b5v+5Zdf6t5775Wrq6u8vb31yCOP2KbNmzdPoaGhKl26tPz8/PTkk0/q+PHjtulXb0VYu3at7SqDTZs2KT09XTExMfLw8JC/v7/GjRtX9DujGCI4FQPp6emKjY3Vjz/+qLi4ODk5OemRRx5Rdna2Xb9Bgwapb9++2rVrl6KiojRx4kSNGzdO7733nn7++WdFRUWpXbt22rdvX57rat68uTZt2mT79mPjxo26//77FRERoY0bN9r6bdy4Uc2bN79u3a+//rr69++vHTt26K677lKnTp1y/Vbljz/+UNOmTVW7dm19/vnnuZ65+u2339SqVSs99thj+vnnn7V48WJt2rRJffr0sev33nvvqV69etq+fbuGDBly3fpQ/Li5uSkzM1NOTk764IMP9Msvv2ju3Ln65ptv9Oqrr9r69e7dWxkZGfrPf/6jnTt3asyYMfLw8JAkDRkyRL/++qvWrl2rXbt2acqUKfL29pb051nOPn36aM6cOWrUqJE2bdqk0NBQ+fr66vnnn1dMTIyWL1+uBQsWaOHChTn+sQaK2j/lGDh69KiWLVumiIiIot8pKFbMPlOdP39eEREROnLkiFauXKmffvpJr776qm366tWr9cgjj+jBBx/U9u3bFRcXp4YNG9qWf/nyZY0YMUI//fSTli9froMHD6pr16456hg0aJDeeecd7dq1S3Xr1tWAAQP073//WytWrNDXX3+t+Ph4JSYm3pR9UqwYuC116dLFePjhh3OdduLECUOSsXPnTsMw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\n"
          },
          "metadata": {}
        }
      ],
      "source": [
        "# Calculate the averages of the metrics\n",
        "avg_jaro_winkler = df[\"jaro_winkler\"].mean()\n",
        "avg_pass_1 = df[\"pass_1\"].mean()\n",
        "avg_pass_3 = df[\"pass_3\"].mean()\n",
        "avg_jaccard = df[\"jaccard\"].mean()\n",
        "\n",
        "# Prepare the data for visualization\n",
        "averages = pd.DataFrame(\n",
        "    {\n",
        "        \"Metric\": [\"Jaro-Winkler\", \"Pass@1\", \"Pass@3\", \"Jaccard\"],\n",
        "        \"Average\": [avg_jaro_winkler, avg_pass_1, avg_pass_3, avg_jaccard],\n",
        "    }\n",
        ")\n",
        "\n",
        "# Create the bar plot\n",
        "plt.figure(figsize=(10, 6))\n",
        "barplot = sns.barplot(x=\"Metric\", y=\"Average\", data=averages)\n",
        "\n",
        "# Annotate the bars with the actual average values\n",
        "for p in barplot.patches:\n",
        "    barplot.annotate(\n",
        "        format(p.get_height(), \".2f\"),\n",
        "        (p.get_x() + p.get_width() / 2.0, p.get_height()),\n",
        "        ha=\"center\",\n",
        "        va=\"center\",\n",
        "        xytext=(0, 9),\n",
        "        textcoords=\"offset points\",\n",
        "    )\n",
        "\n",
        "# Set the title and labels\n",
        "plt.title(\"Evaluation of LLM generated Cypher statements\")\n",
        "plt.ylabel(\"Average\")\n",
        "plt.xlabel(\"Metric\")\n",
        "\n",
        "# Show the plot\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "The evaluation is based on four different metrics:\n",
        "* **Jaro-Winkler**: This metric shows a high average of 0.89, indicating that the LLMs generated Cypher queries that are very similar to the correct Cypher queries on a string level.\n",
        "* **Pass@1**: The average score here is 0.52, suggesting that nearly half of the generated Cypher queries returned the exact same results as the correct query when each query is evaluated independently.\n",
        "* **Pass@3**: With an average of 0.59, this metric indicates an improvement over Pass@1. This suggests that while the LLM may not always get the query right on the first attempt, it often comes up with a correct version within three tries.\n",
        "* **Jaccard Similarity**: The average score of 0.55 is the lowest among the metrics but still indicates that more than half of the time, the sets of results from the LLM-generated Cypher queries share more than half of their elements with the sets from the correct queries.\n",
        "\n",
        "Overall, these metrics suggest that LLMs are decent at generating Cypher queries that are similar to the correct ones and often produce functionally equivalent results, especially when given multiple attempts. However, there is still room for improvement, particularly in generating the correct query on the first attempt. Additionally there is also room for improvement in the evaluation process. Let's take a look at one example:"
      ],
      "metadata": {
        "id": "L-11EpzZqb6r"
      },
      "id": "L-11EpzZqb6r"
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "0b9456f1-17aa-4e30-a9cb-95f65b3bd9bf",
      "metadata": {
        "id": "0b9456f1-17aa-4e30-a9cb-95f65b3bd9bf",
        "outputId": "e5f1d794-b04f-4c56-a96f-a64aa07a1010",
        "colab": {
          "base_uri": "https://localhost:8080/"
        }
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Question: Which directors have never had a movie with a rating below 6.0? \n",
            "\n",
            "True Cypher: MATCH (d:Director)-[:DIRECTED]->(m:Movie) WITH d, MIN(m.imdbRating) AS lowestRating WHERE lowestRating >= 6.0 RETURN d.name, lowestRating \n",
            "\n",
            "Generated Cypher: MATCH (d:Director)-[:DIRECTED]->(m:Movie)\n",
            "WHERE NOT EXISTS {\n",
            "    MATCH (m)-[r:RATED]->(:User)\n",
            "    WHERE r.rating < 6.0\n",
            "}\n",
            "RETURN DISTINCT d.name \n",
            "\n"
          ]
        }
      ],
      "source": [
        "row = df.iloc[24]\n",
        "# Print the desired information\n",
        "print(\"Question:\", row[\"question\"], \"\\n\")\n",
        "print(\"True Cypher:\", row[\"cypher\"], \"\\n\")\n",
        "print(\"Generated Cypher:\", row[\"generated_cypher\"][0], \"\\n\")"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "Another characteristic of LLMs is that they are non-deterministic, meaning you can get different results on every run. Let's run the evaluation three times in sequence now."
      ],
      "metadata": {
        "id": "EuZTAR2uqy3c"
      },
      "id": "EuZTAR2uqy3c"
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "84233300-5d9e-409c-89c4-fe17f2fffddd",
      "metadata": {
        "id": "84233300-5d9e-409c-89c4-fe17f2fffddd",
        "outputId": "9f6f185b-8044-4791-aa53-86b917d751d1",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 165,
          "referenced_widgets": [
            "42a9fdf4f77a4c798362852fc5c3e34c",
            "950dc5427e784fbdba9ae487662bc42e",
            "f8886d2a31a44fffac9d201826090a94",
            "eff8bc8989e3438885294911ca68c929",
            "5a7bfb2e7d1740ba98e5aaeff58bdcef",
            "795bd1d4adf84faaa1a718f3cfe80832",
            "77e094348dc64e1bbf9fa5a44d8c9d87",
            "238208fc07304c63880e4ee0f1c946fd",
            "e88a7b70d655456c9a9dd25fa3b0129b",
            "907e9fc1d774444bb05c45f3fd1b6263",
            "05e6f80442ce4a75949c5493eb7880f5",
            "c4c0aad807884a93a0c379a03a029710",
            "5fcac086d9424225b41b4a266e56b92b",
            "c6631b680c704b66bf6991b4539ce84e",
            "c77ab3ec8a1e4ce99f72e483d03739ec",
            "37ae6876c1914b23adac0ed0808f5c3b",
            "069edfeb29c948518ee6c065ca962383",
            "8abb2cc32c3349e492355cbeda0dd42a",
            "9a153ff9868f4ced8163fef788cbc6a7",
            "8f766e4410504032a94c5693d2111a89",
            "0506a9c946c9457bb8090f05fe2900ec",
            "ac318acb6ae4454f96ff6be5b1d84e8f",
            "a2e175e0fa6846fda12438969e0a6bb2",
            "8b88ad60feaf45b097ca54bf78102c08",
            "994411d6f1b04d93807e6c735a78cc32",
            "bea78544df984dda9a282a13b4b70400",
            "31d420d190e04c1aa0e33f406f8c867c",
            "bf399f53f49e4b858488cffb6d8c3060",
            "dc6cda5d46214c9ea2a39affaf4a2a4a",
            "154eca2b466b483dbdbce96fa3b17c01",
            "8aeb1a55b8474df797857676f465595a",
            "4d5d75fb44ef495380777b2f0537d94b",
            "6e67c70476524e6cb52e99bff088a812"
          ]
        }
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Starting run 1/3\n"
          ]
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "  0%|          | 0/27 [00:00<?, ?it/s]"
            ],
            "application/vnd.jupyter.widget-view+json": {
              "version_major": 2,
              "version_minor": 0,
              "model_id": "42a9fdf4f77a4c798362852fc5c3e34c"
            }
          },
          "metadata": {}
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Starting run 2/3\n"
          ]
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "  0%|          | 0/27 [00:00<?, ?it/s]"
            ],
            "application/vnd.jupyter.widget-view+json": {
              "version_major": 2,
              "version_minor": 0,
              "model_id": "c4c0aad807884a93a0c379a03a029710"
            }
          },
          "metadata": {}
        },
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Starting run 3/3\n"
          ]
        },
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "  0%|          | 0/27 [00:00<?, ?it/s]"
            ],
            "application/vnd.jupyter.widget-view+json": {
              "version_major": 2,
              "version_minor": 0,
              "model_id": "a2e175e0fa6846fda12438969e0a6bb2"
            }
          },
          "metadata": {}
        }
      ],
      "source": [
        "# Number of runs\n",
        "num_runs = 3\n",
        "\n",
        "# A list to collect average metrics across all runs\n",
        "all_runs_averages = []\n",
        "\n",
        "# Perform 3 runs of evaluation\n",
        "for run in range(num_runs):\n",
        "    print(f\"Starting run {run+1}/{num_runs}\")\n",
        "    generated_cyphers = []\n",
        "    true_datas = []\n",
        "    eval_datas = []\n",
        "    jaro_winklers = []\n",
        "    pass_1s = []\n",
        "    pass_3s = []\n",
        "    jaccards = []\n",
        "\n",
        "    # Your data processing code with tqdm progress bar\n",
        "    for index, row in tqdm(df.iterrows(), total=df.shape[0]):\n",
        "        # Fetch data based on the test Cypher statement\n",
        "        true_data = graph.query(row[\"cypher\"])\n",
        "        # Generate 3 Cypher statement and fetch data\n",
        "        example_generated_cyphers = []\n",
        "        example_eval_datas = []\n",
        "        for _ in range(3):\n",
        "            cypher = cypher_chain.invoke({\"question\": row[\"question\"]})\n",
        "            example_generated_cyphers.append(cypher)\n",
        "            # Fetch data based on the generated Cypher statement\n",
        "            try:\n",
        "                example_eval_datas.append(graph.query(cypher))\n",
        "            except ValueError:  # Handle syntax error\n",
        "                example_eval_datas.append([{\"id\": \"Cypher syntax error\"}])\n",
        "        # These metrics require only the first cypher/response\n",
        "        jaro_winkler = get_jw_distance(row[\"cypher\"], example_generated_cyphers[0])\n",
        "        pass_1 = (\n",
        "            1\n",
        "            if df_sim_pair(\n",
        "                (row[\"cypher\"], true_data),\n",
        "                (example_generated_cyphers[0], example_eval_datas[0]),\n",
        "            )\n",
        "            == 1\n",
        "            else 0\n",
        "        )\n",
        "        jaccard = df_sim_pair(\n",
        "            (row[\"cypher\"], true_data),\n",
        "            (example_generated_cyphers[0], example_eval_datas[0]),\n",
        "        )\n",
        "        # Pass@3 check all 3 responses\n",
        "        pass_3 = 1 if any(\n",
        "            df_sim_pair((row[\"cypher\"], true_data), (gen_cypher, eval_data)) == 1\n",
        "            for gen_cypher, eval_data in zip(example_generated_cyphers, example_eval_datas)\n",
        "        ) else 0\n",
        "\n",
        "        # Append the results to their respective lists\n",
        "        generated_cyphers.append(example_generated_cyphers)\n",
        "        true_datas.append(true_data)\n",
        "        eval_datas.append(example_eval_datas)\n",
        "        jaro_winklers.append(jaro_winkler)\n",
        "        pass_1s.append(pass_1)\n",
        "        pass_3s.append(pass_3)\n",
        "        jaccards.append(jaccard)\n",
        "\n",
        "    # Calculate the averages of the metrics for this run\n",
        "    avg_jaro_winkler = sum(jaro_winklers) / len(jaro_winklers)\n",
        "    avg_pass_1 = sum(pass_1s) / len(pass_1s)\n",
        "    avg_pass_3 = sum(pass_3s) / len(pass_3s)\n",
        "    avg_jaccard = sum(jaccards) / len(jaccards)\n",
        "\n",
        "    # Collect averages for this run\n",
        "    run_averages = {\n",
        "        \"Run\": run + 1,\n",
        "        \"Jaro-Winkler\": avg_jaro_winkler,\n",
        "        \"Pass@1\": avg_pass_1,\n",
        "        \"Pass@3\": avg_pass_3,\n",
        "        \"Jaccard\": avg_jaccard,\n",
        "    }\n",
        "    all_runs_averages.append(run_averages)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "6dc6a143-2c8d-460c-aa32-271f441b5a0d",
      "metadata": {
        "id": "6dc6a143-2c8d-460c-aa32-271f441b5a0d",
        "outputId": "e8efdf2c-095d-4242-9c1e-e924f74e56ba",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 564
        }
      },
      "outputs": [
        {
          "output_type": "display_data",
          "data": {
            "text/plain": [
              "<Figure size 1400x600 with 1 Axes>"
            ],
            "image/png": "iVBORw0KGgoAAAANSUhEUgAABIQAAAIjCAYAAAByG8BaAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAACAaElEQVR4nOzdeVhUZf/H8c+ALIqCmrJKkpL7QqES7guFWpptroVaWpnkQlpaKWqluYaZSfm4lEtqZmquKUlP5lYuZea+4QZqKioqKHN+f/RjHkdAQYFR5/26Lq6a+9znnO+Z5Yx8uM99TIZhGAIAAAAAAIDdcLB1AQAAAAAAAChYBEIAAAAAAAB2hkAIAAAAAADAzhAIAQAAAAAA2BkCIQAAAAAAADtDIAQAAAAAAGBnCIQAAAAAAADsDIEQAAAAAACAnSEQAgAAAAAAsDMEQgBwlzCZTBoyZIhN9h0fHy+TyaT4+Hib7D+nZsyYoUqVKsnJyUnFixe3dTmwgXvlvdqlSxcVLVrU1mUAd7WAgAB16dIlR30bN26sxo0b39Z+unTpooCAgNtaFwDuZwRCAHCd6dOny2QyZfuzYcMGW5d4Rz7//HNNnz7d1mXcll27dqlLly4qX768Jk+erC+//DLbvkOGDJHJZNLp06ez7ZMRLMyfP/+m+8147bt165bl8vfee8/S52b7szfDhw/XwoULbV2G9u/fr9dee03lypWTq6ur3N3dVa9ePY0fP16XL1+2dXk2d6ev099//60hQ4bo0KFDeVbT3WDZsmU2C+jt2f36fgKAu1UhWxcAAHejYcOG6aGHHsrUHhgYaINq8s7nn3+uUqVKZfqLbMOGDXX58mU5OzvbprAciI+Pl9ls1vjx4wv8dXB1ddV3332nzz//PNNz9M0338jV1VVXrlwp0JrudsOHD9fzzz+vNm3a2KyGpUuX6oUXXpCLi4siIiJUrVo1paWlae3aterfv7927Nhx02DRHtzp6/T3339r6NChaty48X01AmPZsmWaOHEioVABu9n76ccff7RNUQBwHyMQAoAstGjRQrVq1bJ1GQXGwcFBrq6uti7jpk6ePClJNrlUrHnz5lq8eLGWL1+up59+2tK+bt06HTx4UM8995y+++67Aq+roJjNZqWlpd3175HrHTx4UO3bt1fZsmX1008/ycfHx7KsZ8+e2rdvn5YuXWrDCu+cYRi6cuWKChcubOtScA+5V983d/MfLCQpJSVFbm5uti4DAHKFS8YAIJeuXr2qkiVLqmvXrpmWnT9/Xq6ururXr58kKS0tTYMHD1ZwcLA8PDzk5uamBg0aaM2aNbfcT3ZzHmRcDnW9adOmqWnTpvL09JSLi4uqVKmiSZMmWfUJCAjQjh079PPPP1succqYjyG7eVm+/fZbBQcHq3DhwipVqpRefPFFHTt2LFOdRYsW1bFjx9SmTRsVLVpUpUuXVr9+/ZSenn7L45T+HblUtWpVubi4yNfXVz179tS5c+esao+OjpYklS5dusDnW/Lz81PDhg01e/Zsq/ZZs2apevXqqlatWo63FR8fr1q1asnV1VXly5fXF198keVrKkkzZ860PP8lS5ZU+/btdeTIEas+jRs3VrVq1fT333+rSZMmKlKkiPz8/DRq1KhM20tNTVV0dLQCAwPl4uIif39/vf3220pNTbXqZzKZFBkZqVmzZllelxUrVkiSxowZo7p16+qBBx5Q4cKFFRwcnOmyO5PJpJSUFH311VeW99r1o9KOHTuml19+WV5eXnJxcVHVqlU1derUTPUePXpUbdq0kZubmzw9PdW3b99MtWZn1KhRunjxoqZMmWIVBmUIDAxU7969JUmNGjVSzZo1s9xOxYoVFR4eLkk6dOiQTCaTxowZo08++URly5ZV4cKF1ahRI/31119Zrp+Tz4XZbFZMTIyqVq0qV1dXeXl56bXXXtPZs2et+gUEBOipp57SypUrVatWLRUuXFhffPFFts/B3r179dxzz8nb21uurq4qU6aM2rdvr+TkZEk3f50OHz6sN954QxUrVlThwoX1wAMP6IUXXrC6lGf69Ol64YUXJElNmjSxbOP688jy5cvVoEEDubm5qVixYnryySe1Y8cOqzozziEJCQl66qmnVLRoUfn5+WnixImSpO3bt6tp06Zyc3NT2bJlM30OJencuXPq06eP/P395eLiosDAQI0cOVJms9nS5/rX78svv1T58uXl4uKi2rVr67fffrOqJ2Pf118ynGHOnDkKDg5WsWLF5O7ururVq2v8+PHZvg4ZUlJS9NZbb1lqrFixosaMGSPDMCx9qlWrpiZNmmRa12w2y8/PT88//7xVW368bzLOKX/++acaNWqkIkWKKDAw0PI5//nnnxUSEqLChQurYsWKWr16tdX6ufnuut6t3k83ziGU8b01d+5cvfvuu/L29pabm5tat26d6TyZlZw+f1nJeM/u379fLVu2VLFixdSpUydJ2c+LlF398+bN00cffaQyZcrI1dVVzZo10759+6zWvdVnGQBuFyOEACALycnJmeaDMZlMeuCBB+Tk5KRnnnlGCxYs0BdffGH1V8uFCxcqNTVV7du3l/RvQPSf//xHHTp0UPfu3XXhwgVNmTJF4eHh2rRpk4KCgvKk3kmTJqlq1apq3bq1ChUqpB9++EFvvPGGzGazevbsKUmKiYnRm2++qaJFi+q9996TJHl5eWW7zenTp6tr166qXbu2RowYoaSkJI0fP16//vqrtm7dajVSJz09XeHh4QoJCdGYMWO0evVqjR07VuXLl1ePHj1uWvuQIUM0dOhQhYWFqUePHtq9e7cmTZqk3377Tb/++qucnJwUExOjr7/+Wt9//70mTZqkokWLqkaNGnf+xOVCx44d1bt3b128eFFFixbVtWvX9O233yoqKirHl4tt3bpVzZs3l4+Pj4YOHar09HQNGzZMpUuXztT3o48+0qBBg9S2bVt169ZNp06d0oQJE9SwYcNMz//Zs2fVvHlzPfvss2rbtq3mz5+vd955R9WrV1eLFi0k/fvLT+vWrbV27Vq9+uqrqly5srZv365PPvlEe/bsyTSPzE8//aR58+YpMjJSpUqVsvyCN378eLVu3VqdOnVSWlqa5syZoxdeeEFLlizRk08+Kenfyb+7deumOnXq6NVXX5UklS9fXpKUlJSkxx57zBI6lS5dWsuXL9crr7yi8+fPq0+fPpKky5cvq1mzZkpISFCvXr3k6+urGTNm6KeffsrRc/3DDz+oXLlyqlu37i37vvTSS+revbv++usvq3Dvt99+0549e/T+++9b9f/666914cIF9ezZU1euXNH48ePVtGlTbd++3eozldPPxWuvvWb5vPXq1UsHDx7UZ599pq1bt1o+Axl2796tDh066LXXXlP37t1VsWLFLI8pLS1N4eHhSk1N1Ztvvilvb28dO3ZMS5Ys0blz5+Th4XHT1+m3337TunXr1L59e5UpU0aHDh3SpEmT1LhxY/39998qUqSIGjZsqF69eunTTz/Vu+++q8qVK0uS5b8zZsxQ586dFR4erpEjR+rSpUuaNGmS6tevr61bt1qFBunp6WrRooUaNmyoUaNGadasWYqMjJSbm5vee+89derUSc8++6xiY2MVERGh0NBQy2W9ly5dUqNGjXTs2DG99tprevDBB7Vu3ToNHDhQJ06cUExMjNVzM3v2bF24cEGvvfaaTCaTRo0apWeffVYHDhyQk5OTXnvtNR0/flyrVq3SjBkzrNZdtWqVOnTooGbNmmnkyJGSpJ07d+rXX3+1BIxZMQxDrVu31po1a/TKK68oKChIK1euVP/+/XXs2DF98sknkqR27dppyJAhSkxMlLe3t2X9tWvX6vjx45bvlvx632Q4e/asnnrqKbVv314vvPCCJk2apPbt22vWrFnq06ePXn/9dXXs2FGjR4/W888/ryNHjqhYsWI33eat3Or9lJ2PPvpIJpNJ77zzjk6ePKmYmBiFhYVp27ZtNx0FlZvnLyvXrl1TeHi46tevrzFjxqhIkSK5P2hJH3/8sRwcHNSvXz8lJydr1KhR6tSpkzZu3CgpZ59lALhtBgDAYtq0aYakLH9cXFws/VauXGlIMn744Qer9Vu2bGmUK1fO8vjatWtGamqqVZ+zZ88aXl5exssvv2zVLsmIjo62PO7cubNRtmzZTDVGR0cbN56+L126lKlfeHi4VS2GYRhVq1Y1GjVqlKnvmjVrDEnGmjVrDMMwjLS0NMPT09OoVq2acfnyZUu/JUuWGJKMwYMHW9UpyRg2bJjVNh955BEjODg4076ud/LkScPZ2dl44oknjPT0dEv7Z599Zkgypk6dmum4T506ddNt5rRvxjF/++23N92WJKNnz57GmTNnDGdnZ2PGjBmGYRjG0qVLDZPJZBw6dCjHtbVq1cooUqSIcezYMUvb3r17jUKFClm9pocOHTIcHR2Njz76yGr97du3G4UKFbJqb9SokSHJ+Prrry1tqamphre3t/Hcc89Z2mbMmGE4ODgYv/zyi9U2Y2NjDUnGr7/+anXMDg4Oxo4dOzIdw43vtbS0NKNatWpG06ZNrdrd3NyMzp07Z1r/lVdeMXx8fIzTp09btbdv397w8PCwbD8mJsaQZMybN8/SJyUlxQgMDLR6r2YlOTnZkGQ8/fTT2fa53rlz5wxXV1fjnXfesWrv1auX4ebmZly8eNEwDMM4ePCgIckoXLiwcfToUUu/jRs3GpKMvn37Wtpy+rn45ZdfDEnGrFmzrPqtWLEiU3vZsmUNScaKFStueUxbt27N0fs7u9cpq3PK+vXrM73Xvv322yxfjwsXLhjFixc3unfvbtWemJhoeHh4WLVnPFfDhw+3tJ09e9YoXLiwYTKZjDlz5ljad+3alelc+cEHHxhubm7Gnj17rPY1YMAAw9HR0UhISDAM43+v3wMPPGCcOXPG0m/RokWZzuc9e/bMdJ41DMPo3bu34e7ubly7di3TsptZuHChIcn48MMPrdqff/55w2QyGfv27TMMwzB2795tSDImTJhg1e+NN94wihYtanld8ut9Yxj/O6fMnj3b0pbxvDs4OBgbNmywtGd8F06bNs3SlpvvrrJly1q9/7J7P2XUdf33V8Y53M/Pzzh//rylfd68eYYkY/z48dnWlJvnLysZ79kBAwZkWnbjMd2q/sqVK1v9O2H8+PGGJGP79u2GYeT8swwAt4NLxgAgCxMnTtSqVausfpYvX25Z3rRpU5UqVUpz5861tJ09e1arVq1Su3btLG2Ojo6WEURms1lnzpzRtWvXVKtWLW3ZsiXP6r3+r6AZo5saNWqkAwcO3NaQ8t9//10nT57UG2+8YTVvzJNPPqlKlSplOffK66+/bvW4QYMGOnDgwE33s3r1aqWlpalPnz5ycPjfV1L37t3l7u5+V83xUqJECTVv3lzffPONpH9HGdStW1dly5bN0frp6elavXq12rRpI19fX0t7YGCgZRRPhgULFshsNqtt27Y6ffq05cfb21sPP/xwpksOixYtqhdffNHy2NnZWXXq1LF6/r/99ltVrlxZlSpVstpm06ZNJSnTNhs1aqQqVapkOo7r32tnz55VcnKyGjRokKP3s2EY+u6779SqVSsZhmFVR3h4uJKTky3bWbZsmXx8fKwukSlSpIhlJMvNnD9/XpJyPGLBw8NDTz/9tL755hvL5Tvp6emaO3eu5ZK167Vp00Z+fn6Wx3Xq1FFISIiWLVuWadu3+lx8++238vDw0OOPP271fAQHB6to0aKZXpeHHnrIcgnbrY5JklauXKlLly7dsv+Nrn+dr169qn/++UeBgYEqXrx4jl7rVatW6dy5c+rQoYPVcTk6OiokJCTLy2avv5Nf8eLFVbFiRbm5ualt27aW9ooVK6p48eKZnsMGDRqoRIkSVvsKCwtTenq6/vvf/1rtp127dipRooTlcYMGDSTpluerjLpSUlK0atWqW/a93rJly+To6KhevXpZtb/11lsy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9/niiy+MYsWKGf/880+W/UNDQ41OnTrleN+//fabIcm4cOGCYRj/+z1k4cKFlj4XLlwwnJ2djXnz5lna/vnnH6Nw4cJG7969c7wv3LsYIQSb2bt3rzp06KBy5crJ3d1dAQEBkv6dQPF6tWrVsvz/+fPndfz4cdWrV8+qT7169bRz505J0vDhw1W0aFHLT0JCggIDA1WmTBnFx8fr/Pnz2rp1qxo1aiQfHx89+OCDWr9+vWX+oFuNEKpRo4bl/318fCTJajjm5cuX1aBBAz377LMaP368TCZTttv6448/NH36dKt6w8PDZTabdfDgwSyfAwC3Z8mSJSpatKhcXV3VokULtWvXTkOGDNHq1avVrFkz+fn5qVixYnrppZf0zz//6NKlS5L+neD5ww8/VL169RQdHa0///zTss0ePXpozpw5CgoK0ttvv61169ZZlm3fvl1169aVJK1bt04PPPCAhg4dqqCgIH344YeWS1Olf88lZ8+eLaBnAkBu3Gvnjk8++URbtmzRokWLtH//fkVFReXn0wMgh271u8+2bdv0yCOPqGTJklmuv23bNjVr1izb7W/evFmtWrXSgw8+qGLFillGB97sd6v9+/crLS1NISEhlraSJUuqYsWKt3WMuPcQCMFmWrVqpTNnzmjy5MnauHGj5frYGydNdnNzy9V2X3/9dW3bts3y4+vrK+nf2yeuWbNGv/zyix5++GF5enpKkuWysTVr1igwMFD+/v433b6Tk5Pl/zPCnusvc3NxcVFYWJiWLFmiY8eO3XRbFy9e1GuvvWZV7x9//KG9e/eqfPnyt/0cAMisSZMm2rZtm/bu3avLly/rq6++0qlTp/TUU0+pRo0a+u6777R582ZNnDhR0v/ORd26ddOBAwf00ksvafv27apVq5YmTJggSWrRooUOHz6svn376vjx42rWrJn69esnSbp27ZoKFy5s2daNn+OMS0ckacuWLQoMDMz35wBA7t1r5w5vb29VqlRJrVu31hdffKFJkybpxIkT+fPkAMixW/3uk/G5z87NlqekpCg8PFzu7u6aNWuWfvvtN33//fdW28/A7xW4HoEQbOKff/7R7t279f7776tZs2aqXLlyjv467u7uLl9fX/36669W7b/++quqVKki6d9UOzAw0PJTqFAhSf/+g27dunVatWqVGjdubFm3YcOGio+PV3x8/C1H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\n"
          },
          "metadata": {}
        }
      ],
      "source": [
        "# Convert collected metrics to a DataFrame\n",
        "all_runs_averages_df = pd.DataFrame(all_runs_averages)\n",
        "\n",
        "# Melt the DataFrame for better plotting\n",
        "all_runs_averages_melted = all_runs_averages_df.melt(\n",
        "    id_vars=[\"Run\"], var_name=\"Metric\", value_name=\"Average\"\n",
        ")\n",
        "\n",
        "# Create the bar plot with facet by runs\n",
        "plt.figure(figsize=(14, 6))\n",
        "barplot = sns.barplot(\n",
        "    x=\"Metric\", y=\"Average\", hue=\"Run\", data=all_runs_averages_melted, palette=\"muted\"\n",
        ")\n",
        "\n",
        "# Annotate the bars with the actual average values\n",
        "for p in barplot.patches:\n",
        "    barplot.annotate(\n",
        "        format(p.get_height(), \".2f\"),\n",
        "        (p.get_x() + p.get_width() / 2.0, p.get_height()),\n",
        "        ha=\"center\",\n",
        "        va=\"center\",\n",
        "        xytext=(0, 9),\n",
        "        textcoords=\"offset points\",\n",
        "    )\n",
        "\n",
        "# Set the title and labels\n",
        "plt.title(\"Evaluation of LLM generated Cypher statements over multiple runs\")\n",
        "plt.ylabel(\"Average Score\")\n",
        "plt.xlabel(\"Metric\")\n",
        "plt.legend(title=\"Run\")\n",
        "\n",
        "# Show the plot\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "The bar chart highlights the non-deterministic nature of LLMs. The Jaro-Winkler scores are consistently high across all runs, showing minor fluctuations between 0.88 and 0.89, which indicates stable string similarity of the generated queries. However, for Pass@1, there's a notable variation, with the first run scoring 0.52, and subsequent runs showing scores of 0.59 and 0.48. Pass@3 scores exhibit less variance, hovering around 0.56 to 0.63, suggesting that multiple attempts yield more consistent correct results.\n",
        "\n",
        "## Conclusion\n",
        "\n",
        "Through this blog post, we've learned that LLMs like GPT-4 have a promising capacity for generating Cypher queries, yet the technology isn't foolproof. The evaluation framework presented offers a detailed, quantitative evaluation of LLM performance, allowing you to continuously experiment and update prompt engineering and other steps needed to generate valid and accurate Cypher statements. Additionally, it shows how the non-deterministic nature of LLMs affects the performance from one evaluation to another. Therefore, you can expect similar non-deterministic behavior in production."
      ],
      "metadata": {
        "id": "67tTc3WzrFr6"
      },
      "id": "67tTc3WzrFr6"
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "4055890e-590f-4d7d-bfaa-1ceb20d4d968",
      "metadata": {
        "id": "4055890e-590f-4d7d-bfaa-1ceb20d4d968"
      },
      "outputs": [],
      "source": []
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3 (ipykernel)",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.11.5"
    },
    "colab": {
      "provenance": [],
      "include_colab_link": true
    },
    "widgets": {
      "application/vnd.jupyter.widget-state+json": {
        "ed0715621c1b4a3db11b8733f2b8e1bc": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HBoxModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HBoxModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HBoxView",
            "box_style": "",
            "children": [
              "IPY_MODEL_dfeac02f8f7f40669dbc82c4471bfa5f",
              "IPY_MODEL_8bb7f03b990b403ab4c90d0adb35f588",
              "IPY_MODEL_9cec58549df743309a242bf8c1f1f626"
            ],
            "layout": "IPY_MODEL_a2582b25faed45edac03153dba8bebe3"
          }
        },
        "dfeac02f8f7f40669dbc82c4471bfa5f": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HTMLModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HTMLModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HTMLView",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_bbc3e7f360fa4c61af78efe8c616cbae",
            "placeholder": "​",
            "style": "IPY_MODEL_3c33da6e879a44c4b703c853083063cb",
            "value": "100%"
          }
        },
        "8bb7f03b990b403ab4c90d0adb35f588": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "FloatProgressModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "FloatProgressModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "ProgressView",
            "bar_style": "success",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_9ce518888c5e43f092b1c5d715fd09b7",
            "max": 27,
            "min": 0,
            "orientation": "horizontal",
            "style": "IPY_MODEL_7f52c198bdd447798849fc5b75049ec5",
            "value": 27
          }
        },
        "9cec58549df743309a242bf8c1f1f626": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HTMLModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HTMLModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HTMLView",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_edbcf4fdc0b8403ab53b99e21a81aa24",
            "placeholder": "​",
            "style": "IPY_MODEL_691e71bef0624d97b550ad070a0b7266",
            "value": " 27/27 [05:16&lt;00:00, 16.12s/it]"
          }
        },
        "a2582b25faed45edac03153dba8bebe3": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "bbc3e7f360fa4c61af78efe8c616cbae": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "3c33da6e879a44c4b703c853083063cb": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "DescriptionStyleModel",
          "model_module_version": "1.5.0",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "DescriptionStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "description_width": ""
          }
        },
        "9ce518888c5e43f092b1c5d715fd09b7": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "7f52c198bdd447798849fc5b75049ec5": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "ProgressStyleModel",
          "model_module_version": "1.5.0",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "ProgressStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "bar_color": null,
            "description_width": ""
          }
        },
        "edbcf4fdc0b8403ab53b99e21a81aa24": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "691e71bef0624d97b550ad070a0b7266": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "DescriptionStyleModel",
          "model_module_version": "1.5.0",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "DescriptionStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "description_width": ""
          }
        },
        "42a9fdf4f77a4c798362852fc5c3e34c": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HBoxModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HBoxModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HBoxView",
            "box_style": "",
            "children": [
              "IPY_MODEL_950dc5427e784fbdba9ae487662bc42e",
              "IPY_MODEL_f8886d2a31a44fffac9d201826090a94",
              "IPY_MODEL_eff8bc8989e3438885294911ca68c929"
            ],
            "layout": "IPY_MODEL_5a7bfb2e7d1740ba98e5aaeff58bdcef"
          }
        },
        "950dc5427e784fbdba9ae487662bc42e": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HTMLModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HTMLModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HTMLView",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_795bd1d4adf84faaa1a718f3cfe80832",
            "placeholder": "​",
            "style": "IPY_MODEL_77e094348dc64e1bbf9fa5a44d8c9d87",
            "value": "100%"
          }
        },
        "f8886d2a31a44fffac9d201826090a94": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "FloatProgressModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "FloatProgressModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "ProgressView",
            "bar_style": "success",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_238208fc07304c63880e4ee0f1c946fd",
            "max": 27,
            "min": 0,
            "orientation": "horizontal",
            "style": "IPY_MODEL_e88a7b70d655456c9a9dd25fa3b0129b",
            "value": 27
          }
        },
        "eff8bc8989e3438885294911ca68c929": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HTMLModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HTMLModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HTMLView",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_907e9fc1d774444bb05c45f3fd1b6263",
            "placeholder": "​",
            "style": "IPY_MODEL_05e6f80442ce4a75949c5493eb7880f5",
            "value": " 27/27 [05:56&lt;00:00, 22.12s/it]"
          }
        },
        "5a7bfb2e7d1740ba98e5aaeff58bdcef": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "795bd1d4adf84faaa1a718f3cfe80832": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "77e094348dc64e1bbf9fa5a44d8c9d87": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "DescriptionStyleModel",
          "model_module_version": "1.5.0",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "DescriptionStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "description_width": ""
          }
        },
        "238208fc07304c63880e4ee0f1c946fd": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "e88a7b70d655456c9a9dd25fa3b0129b": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "ProgressStyleModel",
          "model_module_version": "1.5.0",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "ProgressStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "bar_color": null,
            "description_width": ""
          }
        },
        "907e9fc1d774444bb05c45f3fd1b6263": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "05e6f80442ce4a75949c5493eb7880f5": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "DescriptionStyleModel",
          "model_module_version": "1.5.0",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "DescriptionStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "description_width": ""
          }
        },
        "c4c0aad807884a93a0c379a03a029710": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HBoxModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HBoxModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HBoxView",
            "box_style": "",
            "children": [
              "IPY_MODEL_5fcac086d9424225b41b4a266e56b92b",
              "IPY_MODEL_c6631b680c704b66bf6991b4539ce84e",
              "IPY_MODEL_c77ab3ec8a1e4ce99f72e483d03739ec"
            ],
            "layout": "IPY_MODEL_37ae6876c1914b23adac0ed0808f5c3b"
          }
        },
        "5fcac086d9424225b41b4a266e56b92b": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HTMLModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HTMLModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HTMLView",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_069edfeb29c948518ee6c065ca962383",
            "placeholder": "​",
            "style": "IPY_MODEL_8abb2cc32c3349e492355cbeda0dd42a",
            "value": "100%"
          }
        },
        "c6631b680c704b66bf6991b4539ce84e": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "FloatProgressModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "FloatProgressModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "ProgressView",
            "bar_style": "success",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_9a153ff9868f4ced8163fef788cbc6a7",
            "max": 27,
            "min": 0,
            "orientation": "horizontal",
            "style": "IPY_MODEL_8f766e4410504032a94c5693d2111a89",
            "value": 27
          }
        },
        "c77ab3ec8a1e4ce99f72e483d03739ec": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HTMLModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HTMLModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HTMLView",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_0506a9c946c9457bb8090f05fe2900ec",
            "placeholder": "​",
            "style": "IPY_MODEL_ac318acb6ae4454f96ff6be5b1d84e8f",
            "value": " 27/27 [05:06&lt;00:00, 15.56s/it]"
          }
        },
        "37ae6876c1914b23adac0ed0808f5c3b": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "069edfeb29c948518ee6c065ca962383": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "8abb2cc32c3349e492355cbeda0dd42a": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "DescriptionStyleModel",
          "model_module_version": "1.5.0",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "DescriptionStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "description_width": ""
          }
        },
        "9a153ff9868f4ced8163fef788cbc6a7": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "8f766e4410504032a94c5693d2111a89": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "ProgressStyleModel",
          "model_module_version": "1.5.0",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "ProgressStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "bar_color": null,
            "description_width": ""
          }
        },
        "0506a9c946c9457bb8090f05fe2900ec": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "ac318acb6ae4454f96ff6be5b1d84e8f": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "DescriptionStyleModel",
          "model_module_version": "1.5.0",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "DescriptionStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "description_width": ""
          }
        },
        "a2e175e0fa6846fda12438969e0a6bb2": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HBoxModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HBoxModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HBoxView",
            "box_style": "",
            "children": [
              "IPY_MODEL_8b88ad60feaf45b097ca54bf78102c08",
              "IPY_MODEL_994411d6f1b04d93807e6c735a78cc32",
              "IPY_MODEL_bea78544df984dda9a282a13b4b70400"
            ],
            "layout": "IPY_MODEL_31d420d190e04c1aa0e33f406f8c867c"
          }
        },
        "8b88ad60feaf45b097ca54bf78102c08": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HTMLModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HTMLModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HTMLView",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_bf399f53f49e4b858488cffb6d8c3060",
            "placeholder": "​",
            "style": "IPY_MODEL_dc6cda5d46214c9ea2a39affaf4a2a4a",
            "value": "100%"
          }
        },
        "994411d6f1b04d93807e6c735a78cc32": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "FloatProgressModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "FloatProgressModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "ProgressView",
            "bar_style": "success",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_154eca2b466b483dbdbce96fa3b17c01",
            "max": 27,
            "min": 0,
            "orientation": "horizontal",
            "style": "IPY_MODEL_8aeb1a55b8474df797857676f465595a",
            "value": 27
          }
        },
        "bea78544df984dda9a282a13b4b70400": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "HTMLModel",
          "model_module_version": "1.5.0",
          "state": {
            "_dom_classes": [],
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "HTMLModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/controls",
            "_view_module_version": "1.5.0",
            "_view_name": "HTMLView",
            "description": "",
            "description_tooltip": null,
            "layout": "IPY_MODEL_4d5d75fb44ef495380777b2f0537d94b",
            "placeholder": "​",
            "style": "IPY_MODEL_6e67c70476524e6cb52e99bff088a812",
            "value": " 27/27 [05:04&lt;00:00, 13.06s/it]"
          }
        },
        "31d420d190e04c1aa0e33f406f8c867c": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "bf399f53f49e4b858488cffb6d8c3060": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "dc6cda5d46214c9ea2a39affaf4a2a4a": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "DescriptionStyleModel",
          "model_module_version": "1.5.0",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "DescriptionStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "description_width": ""
          }
        },
        "154eca2b466b483dbdbce96fa3b17c01": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "8aeb1a55b8474df797857676f465595a": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "ProgressStyleModel",
          "model_module_version": "1.5.0",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "ProgressStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "bar_color": null,
            "description_width": ""
          }
        },
        "4d5d75fb44ef495380777b2f0537d94b": {
          "model_module": "@jupyter-widgets/base",
          "model_name": "LayoutModel",
          "model_module_version": "1.2.0",
          "state": {
            "_model_module": "@jupyter-widgets/base",
            "_model_module_version": "1.2.0",
            "_model_name": "LayoutModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "LayoutView",
            "align_content": null,
            "align_items": null,
            "align_self": null,
            "border": null,
            "bottom": null,
            "display": null,
            "flex": null,
            "flex_flow": null,
            "grid_area": null,
            "grid_auto_columns": null,
            "grid_auto_flow": null,
            "grid_auto_rows": null,
            "grid_column": null,
            "grid_gap": null,
            "grid_row": null,
            "grid_template_areas": null,
            "grid_template_columns": null,
            "grid_template_rows": null,
            "height": null,
            "justify_content": null,
            "justify_items": null,
            "left": null,
            "margin": null,
            "max_height": null,
            "max_width": null,
            "min_height": null,
            "min_width": null,
            "object_fit": null,
            "object_position": null,
            "order": null,
            "overflow": null,
            "overflow_x": null,
            "overflow_y": null,
            "padding": null,
            "right": null,
            "top": null,
            "visibility": null,
            "width": null
          }
        },
        "6e67c70476524e6cb52e99bff088a812": {
          "model_module": "@jupyter-widgets/controls",
          "model_name": "DescriptionStyleModel",
          "model_module_version": "1.5.0",
          "state": {
            "_model_module": "@jupyter-widgets/controls",
            "_model_module_version": "1.5.0",
            "_model_name": "DescriptionStyleModel",
            "_view_count": null,
            "_view_module": "@jupyter-widgets/base",
            "_view_module_version": "1.2.0",
            "_view_name": "StyleView",
            "description_width": ""
          }
        }
      }
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}